/**********************************************************************************************************************
 *
 * Project:    mGameEngine
 * File:       character.cpp
 * Author:     twoja stara
 * Created on: 201-04-02
 *
 **********************************************************************************************************************/

#include "character.h"
#include "userallocator.h"

namespace mGameEngine
{
namespace Physics
{

NxControllerManager *Character::_manager = NULL;

Character::Character() :
    Actor(),
    _height(2.0f), _width(1.0f),
    _speed(1.0f), _acc(1.0f), _dec(1.0f),
    _jump(1.0f), _jumpControl(0.5f),
    _step(0.25f), _slope(M_PI_4),
     _floor(false), _vel(), _controller(NULL)
{
}

Character::~Character()
{
    // delete physx controller
    if(_manager && _controller)
    {
        _manager->releaseController(*_controller);
    }

    // release physx controller manager
    if(_manager && _manager->getNbControllers() == 0)
    {
        NxReleaseControllerManager(_manager);
        _manager = NULL;
    }
}

NxActor *Character::_createJointActor(Joint *joint) const
{
    return _controller ? _controller->getActor() : NULL;
}

NxControllerAction Character::onShapeHit(const NxControllerShapeHit& hit)
{
//    // too big movement
//    if(hit.length > 0.1f)
//    {
//        return NX_ACTION_NONE;
//    }

    // push dynamic actors
    NxActor &actor = hit.shape->getActor();
    if(actor.isDynamic())
    {
        float m = _controller->getActor()->getMass();
        float v = hit.length;
        NxVec3 i =  m * v * hit.dir;
        NxVec3 pos(hit.worldPos.x, hit.worldPos.y, hit.worldPos.y);
        actor.addForceAtPos(i, pos, NX_IMPULSE);
    }

    return NX_ACTION_NONE;
}

NxControllerAction Character::onControllerHit(const NxControllersHit& hit)
{
    return NX_ACTION_NONE;
}

void Character::setWidth(float width)
{
    // only positive widths
    if(width <= 0.0f)
    {
        return;
    }

    _width = width;

    // update physx controller 
    if(_controller)
    {
        _controller->setRadius(0.5f*_width);
    }
}

void Character::setHeight(float height)
{
    // only positive heights
    if(height <= 0.0f)
    {
        return;
    }

    _height = height;

    // update physx controller 
    if(_controller)
    {
        _controller->setHeight(max(0.01f, _height - _width));
    }
}

void Character::setSpeed(float speed)
{
    // only positive speeds
    if(speed <= 0.0f)
    {
        return;
    }

    _speed = speed;
}


void Character::setAcceleration(float acc)
{
    // only positive accelerations
    if(acc <= 0.0f)
    {
        return;
    }

    _acc = acc;
}

void Character::setDecelartion(float dec)
{
    // only positive decelerations
    if(dec <= 0.0f)
    {
        return;
    }

    _dec = dec;
}

void Character::setJumpHeight(float jump)
{
    // no negative jump heights
    if(jump < 0.0f)
    {
        return;
    }

    _jump = jump;
}

void Character::setJumpControl(float jumpControl)
{
    // only between 0.0 and 1.0
    if(jumpControl < 0.0f || jumpControl > 1.0f)
    {
        return;
    }

    _jumpControl = jumpControl;
}

void Character::setStepHeight(float step)
{
    // no negative step heights
    if(step < 0.0f)
    {
        return;
    }

    _step = step;

    // update physx controller
    if(_controller)
    {
        _controller->setStepOffset(_step);
    }
}

void Character::setSlope(float slope)
{
    // slopes only up to 90 degrees
    if(slope < 0.0f || slope > M_PI_2)
    {
        return;
    }

    _slope = slope;

    // TODO update physx controller
    if(_controller)
    {
    }
}

void Character::putForce(const Vector3 &force, const Vector3 &radius)
{
}

void Character::putImpulse(const Vector3 &impulse, const Vector3 &radius)
{
}

void Character::putAcceleration(const Vector3 &acc, const Vector3 &radius)
{
}


void Character::update(float dt, bool forward, bool backward, bool left, bool right, float yaw, bool jump)
{
    // accelerate
    if(forward ^ backward || left ^ right)
    {
        Vector3 target = Quaternion(yaw, 0.0f, 0.0f) * 
            Vector3(forward && !backward ? _speed : (!forward && backward ? -_speed : 0.0f),
            left && !right? _speed : (!left && right ? -_speed : 0.0f),
            _vel.z);
        Vector3 dv = target - _vel;
        float acc = _floor ? _acc : _jumpControl * _acc;
        if(length(dv) > acc * dt)
        {
            _vel += dv.normalize() * acc * dt;
        }
        else
        {
            _vel = target;
        }
    }
    // decelerate
    else
    {
        Vector3 dv = -_vel;
        float dec = _floor ? _dec : _jumpControl * _dec;
        if(length(dv) > dec * dt)
        {
            _vel += dv.normalize() * dec * dt;
        }
        else
        {
            _vel.x = _vel.y = 0.0f;
        }
    }

    // gravity
    _vel.z += -9.81f * dt;

    // jump
    if(jump && _floor)
    {
        _floor = false;
        _vel.z = sqrt(2.0f * 9.81f * _jump);
    }

    // update physx controller
    if(_controller)
    {
        NxU32 flags;
        _controller->move(NxVec3(_vel) * dt, 0xffffffff, 0.01f * dt, flags, 0.5f);
        
        // check for floor collision
        if(flags & NXCC_COLLISION_DOWN)
        {
            _floor = true;
            _vel.z = 0.0f;
        }
        else if(_vel.z < -0.5f)
        {
            _floor = false;
        }

        // check for ceiling collision
        if(flags & NXCC_COLLISION_UP)
        {
            _vel.z = 0.0f;
        }

        // check for sides collision
        if(flags & NXCC_COLLISION_SIDES)
        {
            _vel.x *= 0.5f;
            _vel.y *= 0.5f;
        }
    }
}

void Character::create(NxScene *scene) const
{
    // already created
    if(_created)
    {
        return;
    }

    // create physx controllers manager
    if(!_manager)
    {
        _manager = NxCreateControllerManager(new UserAllocator());
    }

    // create physx controller
    NxCapsuleControllerDesc desc;
    desc.height = max(0.01f, _height - _width);
    desc.radius = 0.5f*_width;
    desc.stepOffset = _step;
    desc.slopeLimit = cosf(_slope);
    desc.upDirection = NX_Z;
    desc.skinWidth = 0.01f;
    desc.callback = (NxUserControllerHitReport *)this;
    _controller = (NxCapsuleController *)_manager->createController(scene, desc);

    //  is created
    _created = true;
}

Matrix4x4 Character::getTransformation() const
{
    if(!_controller)
    {
        return Matrix4x4();
    }

    const NxVec3 &p = _controller->getActor()->getGlobalPosition();
    return Matrix4x4(0.0f, 0.0f, 0.0f, p.x,
                     0.0f, 0.0f, 0.0f, p.y,
                     0.0f, 0.0f, 0.0f, p.z,
                     0.0f, 0.0f, 0.0f, 1.0f);
}

void Character::setTransformation(const Matrix4x4 &t)
{
    // update physx controller
    if(_controller)
    {
        _controller->setPosition(NxExtendedVec3(t._m[12], t._m[13], t._m[14]));
    }
}

Vector3 Character::getPointVelocity(const Vector3 &p)
{
    Vector3 v;
    if(_controller)
    {
        // TODO
    }
    return v;
}

}
}
